//通用工具模块：提供一些通用的工具函数，如去除字符串末尾的换行符、安全复制字符串、检查字符串是否为空或只包含空白字符、检查用户名是否合法等
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"
#include <stdbool.h>
/**
 *  去除字符串末尾的换行符。
 * 
 * 该函数会检查传入字符串的末尾字符是否为换行符 `\n`，
 * 若为换行符则将其替换为字符串结束符 `\0`，
 * 并返回处理后的字符串长度。
 * 
 *  str 要处理的字符串，以 null 结尾的字符数组。
 * return size_t 处理后的字符串长度。
 */
//size_t 是一个无符号整数类型，
//用于表示对象的大小或数量
size_t trim_newline(char *str) {
    // 获取字符串的长度
    size_t len = strlen(str);
    // 检查字符串长度是否大于 0 且末尾字符为换行符
    if (len > 0 && str[len - 1] == '\n') {
        // 将末尾的换行符替换为字符串结束符
        str[len - 1] = '\0';
        // 返回处理后的字符串长度
        return len - 1;
    }
    // 若末尾不是换行符，直接返回原始字符串长度
    return len;
}

/**
 *  去除字符串首尾的空白字符。
 * 
 * 该函数会修改传入的字符串，移除其开头和结尾的空白字符，
 * 包括空格、制表符、换行符等。若字符串为空或为 NULL，函数将直接返回。
 * 
 *   str 待处理的字符串。
 */
void trim_string(char *str) {
    // 检查字符串是否为 NULL 或者为空字符串，如果是则直接返回
    if (str == NULL || *str == '\0') {
        return;
    }

    // 指向字符串的起始位置
    char *start = str;
    // 指向字符串的最后一个字符位置
    char *end = str + strlen(str) - 1;  //-1是因为字符串长度不包括结束符

    // 去除字符串首部的空白字符，移动 start 指针直到遇到非空白字符
    //isspace函数检查给定的字符是否为空白字符（如空格、制表符、换行符等）。是--返回非零。不是--返回0
    //(unsigned char)*start:将start指针当前指向的字符强制转换为无符号字符。
    while (isspace((unsigned char)*start)) {
        start++;
    }

    // 去除字符串尾部的空白字符，移动 end 指针直到遇到非空白字符
    //end > start：保证end指针在start指针之后，当他俩重合时，说明已经处理完毕
    while (end > start && isspace((unsigned char)*end)) {
        end--;
    }

    // 判断start指针是否发生了移动
    // 如果 start 指针发生了移动，说明字符串开头有空白字符，
    // 需要将非空白部分移动到字符串的起始位置
    // 否则，说明字符串没有空白字符，不需要移动
    if (start != str) {
        // 使用 memmove 函数将非空白部分从 start 位置复制到 str 位置
        //str指向的是原始字符串的起始位置，
        //start指向的是处理后的非空白部分的起始位置。
        //end - start + 1：计算非空白部分的长度（包括结束符，便于后面添加）。
        memmove(str, start, end - start + 1);
    }

    // 在处理后的字符串末尾添加 null 结束符
    str[end - start + 1] = '\0';
}

/**
 * 安全复制字符串，防止缓冲区溢出
 * 
 *  dest 目标缓冲区
 *  src 源字符串
 * dest_size 目标缓冲区大小
 * return 成功复制的字节数，-1表示失败
 */
/**
 *  安全复制字符串，防止缓冲区溢出。
 * 
 * 该函数将源字符串复制到目标缓冲区，会检查目标缓冲区的大小，
 * 避免因源字符串过长而导致缓冲区溢出。若源字符串长度超过目标缓冲区大小，
 * 会截断源字符串并确保目标字符串以空字符结尾。
 * 
 * dest 目标缓冲区，用于存储复制后的字符串。
 *  src 源字符串，要被复制的字符串。
 *  dest_size 目标缓冲区的大小，以字节为单位。
 * return int 成功复制的字节数，若参数无效则返回 -1。
 */
int safe_strcpy(char *dest, const char *src, size_t dest_size) {
    // 检查参数有效性，
    //若目标缓冲区指针为空、源字符串指针为空或目标缓冲区大小为 0，
    //返回 -1 表示失败
    if (!dest || !src || dest_size == 0) {
        return -1;
    }
    
    // 获取源字符串的长度
    size_t src_len = strlen(src);
    // 检查源字符串长度是否大于等于目标缓冲区大小
    if (src_len >= dest_size) {
        // 若源字符串过长，
        //则使用 strncpy 函数将源字符串截断复制到目标缓冲区，
        //预留一个字节给字符串结束符
        strncpy(dest, src, dest_size - 1);
        // 手动添加字符串结束符，确保目标字符串以 '\0' 结尾
        dest[dest_size - 1] = '\0';
        // 返回实际复制的字节数，即目标缓冲区大小减 1
        return dest_size - 1;
    }
    
    // 若源字符串长度小于目标缓冲区大小，使用 strcpy 函数将源字符串完整复制到目标缓冲区
    strcpy(dest, src);
    // 返回源字符串的长度，即实际复制的字节数
    return src_len;
}

/**
 * 检查字符串是否为空或只包含空白字符
 * 
 *  str 要检查的字符串
 * 返回值1表示为空或空白，0表示非空
 */
/**
 *   检查字符串是否为空或只包含空白字符。
 * 
 * 该函数会遍历传入的字符串，判断其是否为空指针、空字符串或者仅由空白字符组成。
 * 若字符串为空指针、空字符串或仅包含空白字符，返回 1；否则返回 0。
 * 
 *   str 要检查的字符串，以 null 结尾的字符串指针。
 *  return int 1 表示字符串为空或只包含空白字符，0 表示字符串包含非空白字符。
 */
int is_empty_or_whitespace(const char *str) {
    // 若传入的字符串指针为空，认为字符串为空，直接返回 1
    if (!str) return 1;
    
    // 遍历字符串中的每个字符
    while (*str) {
        // 检查当前字符是否不是空白字符
        if (!isspace((unsigned char)*str)) {
            // 若存在非空白字符，返回 0
            return 0;
        }
        // 移动到下一个字符
        str++;
    }
    // 若遍历完整个字符串都未发现非空白字符，返回 1
    return 1;
}

/**
 *  获取当前时间戳字符串。
 * 
 * 该函数使用 time 函数获取当前时间的秒数表示，
 * 然后使用 localtime 函数将秒数转换为本地时间，
 * 最后使用 strftime 函数将时间格式化为指定的字符串形式。
 * 
 *  buffer 存储时间戳的缓冲区，用于存储格式化后的时间字符串。
 *  size 缓冲区大小，确保有足够的空间存储时间字符串。
 */
void get_timestamp(char *buffer, size_t size) {
    time_t now = time(NULL);    //获取当前时间的秒数表示
    struct tm *t = localtime(&now); //将秒数转换为本地时间
    strftime(buffer, size, "%Y-%m-%d %H:%M:%S", t); //将时间格式化为字符串
}

/**
 * 创建目录（如果不存在）
 * 
 *  path 目录路径
 * return 0表示成功，-1表示失败
 */
/**
 *  创建指定目录，如果该目录不存在。
 * 
 * 该函数首先检查指定路径的目录是否存在。如果目录不存在，
 * 则尝试创建该目录。若创建过程中出现错误，会打印错误信息。
 * 
 *  path 要检查并创建的目录路径，以 null 结尾的字符串。
 * return int 操作成功返回 0，创建目录失败返回 -1。
 */
int create_directory_if_not_exists(const char *path) {
    // 定义一个 stat 结构体，用于存储文件或目录的状态信息
    struct stat st = {0};
    // 使用 stat 函数检查指定路径的目录是否存在
    // stat 函数返回 -1 表示指定路径的目录不存在
    if (stat(path, &st) == -1) {
        // 若目录不存在，尝试使用 mkdir 函数创建该目录
        // 0700 表示设置目录的权限为用户具有读、写、执行权限
        if (mkdir(path, 0700) == -1) {
            // 若创建目录失败，使用 perror 打印错误信息
            perror("创建目录失败");
            // 返回 -1 表示创建目录操作失败
            return -1;
        }
    }
    // 若目录已存在或成功创建，返回 0 表示操作成功
    return 0;
}
